#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/time.h>

#define PACKET_SIZE 4096

unsigned short checksum(void *b, int len) {
    unsigned short *buf = (unsigned short *)b;
    unsigned int sum = 0;
    unsigned short result;

    for (sum = 0; len > 1; len -= 2)
        sum += *buf++;
    if (len == 1)
        sum += *(unsigned char *)buf;
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    result = ~sum;
    return result;
}

int main() {
    int sockfd;
    char packet[PACKET_SIZE];
    struct sockaddr_in addr;
    struct icmphdr *icmp;
    struct sockaddr_in from;
    socklen_t fromlen = sizeof(from);
    int bytes;

    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
        perror("socket");
        return 1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    std::cout << "ICMP PING Server started. Waiting for ICMP Echo Requests..." << std::endl;

    while (1) {
        bytes = recvfrom(sockfd, packet, sizeof(packet), 0, (struct sockaddr *)&from, &fromlen);
        if (bytes < 0) {
            perror("recvfrom");
            continue;
        }

        icmp = (struct icmphdr *)(packet + (sizeof(struct iphdr)));

        if (icmp->type == ICMP_ECHO) {
            char src_ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &(from.sin_addr), src_ip, INET_ADDRSTRLEN);
            std::cout << "Received ICMP Echo Request from " << src_ip << std::endl;

            struct icmphdr reply_icmp;
            reply_icmp.type = ICMP_ECHOREPLY;
            reply_icmp.code = 0;
            reply_icmp.un.echo.id = icmp->un.echo.id;
            reply_icmp.un.echo.sequence = icmp->un.echo.sequence;
            reply_icmp.checksum = 0;
            reply_icmp.checksum = checksum(&reply_icmp, sizeof(reply_icmp));

            if (sendto(sockfd, &reply_icmp, sizeof(reply_icmp), 0, (struct sockaddr *)&from, fromlen) < 0) {
                perror("sendto");
            }
        }
    }

    close(sockfd);
    return 0;
}